package wangxiaotao.service.impl;

import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.CircleCaptcha;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import wangxiaotao.bean.vo.AccountVO;
import wangxiaotao.bean.vo.UserVO;
import wangxiaotao.common.CommonResult;
import wangxiaotao.converter.UserConverter;
import wangxiaotao.entity.User;
import wangxiaotao.mapper.UserMapper;
import wangxiaotao.service.CommonService;
import wangxiaotao.shiro.cache.RedisCacheManager;
import wangxiaotao.shiro.realms.CustomRealm;
import wangxiaotao.shiro.token.CustomToken;
import wangxiaotao.tools.ConstValue;
import wangxiaotao.utils.JWTUtils;
import wangxiaotao.utils.RedisUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
@Service
@RequiredArgsConstructor
public class CommonServiceImpl implements CommonService {
    private final UserMapper userMapper;
    private final ConstValue constValue;
    private final UserConverter userConverter;
    private final CustomRealm customRealm;
    private final RedisCacheManager redisCacheManager;

    public final static String tokenKeyPrefix = "token:cache:";
    private final static int loginLimit = 5;

    /**
     * 登录逻辑
     * @param request
     * @param response
     * @param accountVO
     * @return
     */
    @Override
    public CommonResult login(HttpServletRequest request, HttpServletResponse response, AccountVO accountVO) {
        User user = userMapper.selectOne(new QueryWrapper<User>().eq("username", accountVO.getUsername()));
        Assert.notNull(user, "用户不存在");
        Assert.isTrue(user.getStatue(), "锁定的账号");
        //登录尝试
        RetryLimit(user, accountVO);

        //是否已经登录过 处理
        UserVO userVO = userConverter.toUserVO(user);
        KikOutUser(userVO);

        //创建token
        String token = JWTUtils.createToken(user.getId());
        CustomToken customToken = new CustomToken(token);
        Subject subject = SecurityUtils.getSubject();
        //这里传入用户认证凭证
        subject.login(customToken);
        //存储token到redis
        RedisUtils.set(getTokenKeyPrefix(user.getId()),JWTUtils.getId(token),redisCacheManager.getExpire());
        log.info("-------------------->存储tokenId到redis:{}",token);
        //设置头
        response.setHeader("authorization", token);
        response.setHeader("Access-Control-Allow-Headers", "authorization");
        response.setHeader("Access-Control-Expose-Headers", "authorization");
        boolean[] booleans = subject.hasRoles(Arrays.asList(constValue.getDefaultAdminRole().split(",")));
        for (boolean b : booleans) {
            if (b) {//如果有管理员界面的权限
                return CommonResult.success(300, null, userVO);//管理员
            }
        }
        return CommonResult.success(200, null, userVO);
    }

    /**
     * 获取验证码图片 base64格式
     * @return
     */
    @Override
    public CircleCaptcha getCaptcha() {
        return CaptchaUtil.createCircleCaptcha(100, 40,5,12);
    }

    /**
     * 登录尝试
     * @param user
     * @param accountVO
     */
    private void RetryLimit(User user,AccountVO accountVO){
        log.info("------------>login RetryLimit限制登录次数");
        //自定义尝试次数存储到redis存储key的前缀
        String realKey = "shiro:cache:retrylimit:" + user.getUsername();
        //获取redis中该用户尝试的次数
        AtomicInteger frequency = (AtomicInteger)RedisUtils.get(realKey);
        log.info("当前{}登录次数:{}",accountVO,frequency);
        if (frequency == null){//如果第一次尝试
            frequency = new AtomicInteger(0);
        }
        if (frequency.incrementAndGet() > loginLimit) {//锁定操作
            //失败次数大于规定次数 执行锁定逻辑
            if (user.getStatue()){//正常状态
                user.setStatue(false);//修改数据库的状态字段为锁定
                //更新数据
                userMapper.update(user, new UpdateWrapper<User>().set(true, "statue", false).eq("id", user.getId()));
                log.info("------------>login RetryLimit锁定用户：" + user.getUsername());
            }
            RedisUtils.remove(realKey);//清除记录次数
            throw new LockedAccountException();//抛出用户锁定异常
        }
        //密码正确
        if (user.getPassword().equals(accountVO.getPassword())){
            //登录最近时间设置
            userMapper.update(user,new UpdateWrapper<User>().set(true,"last_login_time", LocalDateTime.now()).eq("id",user.getId()));
            RedisUtils.remove(realKey);//清除记录次数
        }else {//密码错误
            RedisUtils.set(realKey,frequency,60);//设置redis的key的超时时间，超过60s忽略
            throw new IncorrectCredentialsException();//抛出密码错误异常
        }
    }

    /**
     * 判断是否登录过 登录过就踢出前一个
     * @param user
     */
    private void KikOutUser(UserVO user){
        //获取缓存的认证信息
        Set<Object> authentications = customRealm.getAuthenticationCache().keys();
        if (authentications.isEmpty())
            return;
        for (Object name:authentications){
            //存在已经登录的账号
            if (((String)name).contains(String.valueOf(user.getId()))){
                //从redis里获取缓存的数据
                AuthenticationInfo authenticationInfo = (AuthenticationInfo) RedisUtils.get(name);
                //清除登录过的缓存
                customRealm.clearCache(authenticationInfo.getPrincipals());
            }
        }
    }
    public static String getTokenKeyPrefix(Integer id){
        return tokenKeyPrefix+id;
    }
}
